#include "spline.h"

//==============================================================================
// Global variables for debugging
//==============================================================================
int NumPointsRendered = 0;
int NumLinesRendered = 0;
int NumSegmentsRendered = 0;
int NumSubSplines = 0;

bool UseParticles = true;

G3::H_TEXTURE hFogMode, hTorusMode;
G3::GUI::IMAGE DrawModeIndicator;

//==============================================================================
// GLU Quadric objects that are used to draw the spline
//==============================================================================
GLUquadricObj *cylinder = NULL;
GLUquadricObj *sphere = NULL;

//==============================================================================
// Maximal vertex snapping distance
//==============================================================================
float Epsilon = 0.00001f;

//==============================================================================
// The whole drawn spline
//==============================================================================
DRAWN_SPLINE Spline;

//==============================================================================
// Constructors & Destructors
//==============================================================================
SUBSPLINE::SUBSPLINE () {
    Radius = 0.01f;
	DLID = 0;
	Compiling = 0;

	iterations = 0;
}

SUBSPLINE::~SUBSPLINE () {
    Clear ();
}

DRAWN_SPLINE::DRAWN_SPLINE () {
    Radius = 0.01f;
}

DRAWN_SPLINE::~DRAWN_SPLINE () {
    SubSplines.clear ();
}

//==============================================================================
// = operators
//==============================================================================
SUBSPLINE& SUBSPLINE::operator= (const SUBSPLINE &sspl) {
    Points = sspl.Points;
    Radius = sspl.Radius;
	DLID = sspl.DLID;
	Compiling = sspl.Compiling;

    return *this;
}

void SUBSPLINE::Compile () {
	if (Compiling == 0) {
		DLID = glGenLists (1);
		glErrCheck (&G3::MainLog, "glGenLists", FILE_LINE);
		Compiling = 1;
	}
}

//==============================================================================
// Adds a point to a subspline
//==============================================================================
void SUBSPLINE::AddPoint (G3::VEC3F aPoint) {
    G3::GUI::PROFILER prof ("Spline::AddPoint");

    if (!Points.empty ()) {
        if (G3_ABS (Points.back().x - aPoint.x) < Epsilon &&
            G3_ABS (Points.back().y - aPoint.y) < Epsilon &&
            G3_ABS (Points.back().z - aPoint.z) < Epsilon) {

            // There already is a point there
            return;
        }
    }

    Points.push_back (aPoint);

    // Update the particle system position
    Spline.PSys.Position = aPoint;
    // And emit a particle
    Spline.PSys.Emit();
}

//==============================================================================
// Renders a line in a subspline
//==============================================================================
void SUBSPLINE::RenderLine (std::list<G3::VEC3F>::iterator it, int Segments, float aElapsedTime) {
    G3::GUI::PROFILER prof ("Spline::RenderLine");

    std::list<G3::VEC3F>::iterator nit = it;
    nit++;

    if (it == Points.end() || nit == Points.end())
        return;

    NumLinesRendered++;

    double angle = 0.0;
    G3::VEC3F v = *nit - *it;
    G3::VEC3F a = v.GetSlopeAngle (&angle);

    glPushMatrix ();
        glTranslatef (it->x, it->y, it->z);
        glRotatef ((float) G3_RAD2DEG (angle), a.x, a.y, 0.0f);
        gluCylinder (cylinder, Radius, Radius, v.GetLength (), Segments, Segments);
    glPopMatrix ();
}

//==============================================================================
// Renders a point in a subspline
//==============================================================================
void SUBSPLINE::RenderPoint (std::list<G3::VEC3F>::iterator it, int Segments, float aElapsedTime) {
    G3::GUI::PROFILER prof ("Spline::RenderPoint");

    if (it == Points.end())
        return;

    NumPointsRendered++;

    glPushMatrix ();
        glTranslatef (it->x, it->y, it->z);
        gluSphere (sphere, Radius, Segments, Segments);
    glPopMatrix ();
}

//==============================================================================
// Renders a subspline
//==============================================================================
void SUBSPLINE::Render (float aElapsedTime) {
    G3::GUI::PROFILER prof ("Spline::Render");

    int NumSegments = 0;
    float Distance = 0.0f;
    G3::AABB3F bbox;

    glDisable (GL_TEXTURE_2D);

	if (Compiling != 2) {
		if (Compiling == 1) {
			glNewList (DLID, GL_COMPILE);
			glErrCheck (&G3::MainLog, "glNewList", FILE_LINE);
		}

		glColor3f (G3CS_Fg_Color.r, G3CS_Fg_Color.g, G3CS_Fg_Color.b);

        if (Points.size () < 2)
            return;

        std::list<G3::VEC3F>::iterator it = Points.begin();
        std::list<G3::VEC3F>::iterator pit = it;
        while (it != Points.end()) {
            if (pit != Points.end()) {
		        bbox.Minimum = *pit;
		        bbox.Maximum = *pit;

                Assert( Points.size () >= 2, "WTF error" );

		        bbox.Expand (*it);

		        bbox.Minimum.x -= Radius;
		        bbox.Minimum.y -= Radius;
		        bbox.Minimum.z -= Radius;

		        bbox.Minimum.x += Radius;
		        bbox.Minimum.y += Radius;
		        bbox.Minimum.z += Radius;

                // Is the AABB of the line inside the viewing frustum?
		        if (Compiling == 1 || frame.Renderer.Camera.ViewFrustum.IsBoxIn (bbox) != G3::GFC_OUT) {
                    // Distance LOD on the number of segments
		            Distance = (frame.Renderer.Camera.Position - (bbox.Minimum + bbox.Maximum) / 2).GetLength () / 3.0f;
		            NumSegments = (int) (((float) G3CS_Subdivisions - 3) / Distance) + 3;

		            NumSegmentsRendered += NumSegments;

                    // Render the line
		            RenderLine (pit, NumSegments);
		        }
		    }

            // Is the SPHERE of the point inside the viewing frustum?
		    if (Compiling == 1 || frame.Renderer.Camera.ViewFrustum.IsSphereIn (G3::BSPH3F (*it, Radius)) != G3::GFC_OUT) {
                // Distance LOD on the number of segments
		        Distance = (frame.Renderer.Camera.Position - *it).GetLength () / 3.0f;
		        NumSegments = (int) (((float) G3CS_Subdivisions - 3) / Distance) + 3;

		        NumSegmentsRendered += NumSegments;

                // Render the point
		        RenderPoint (it, NumSegments);
		    }

            // Take the next point
            pit = it;
            it++;
		}

		if (Compiling == 1) {
			glEndList ();
			glErrCheck (&G3::MainLog, "glEndList", FILE_LINE);
			Compiling = 2;
		}
	} else {
        glListBase (0);
		glErrCheck (&G3::MainLog, "glListBase", FILE_LINE);
        glCallList (DLID);
		glErrCheck (&G3::MainLog, "glCallList", FILE_LINE);
	}

    glEnable (GL_TEXTURE_2D);
}

void SUBSPLINE::Filter () {
	if (iterations >= G3CS_WII_NoiseFilterIterations)
		return;

	G3::VEC3D umbrella;

    std::list<G3::VEC3F>::iterator nit = Points.begin();
    std::list<G3::VEC3F>::iterator it = nit;
    std::list<G3::VEC3F>::iterator pit = it;
    while (nit != Points.end()) {
        if (it != Points.begin()) {
		    umbrella.x = 0.5 * (pit->x + nit->x) - it->x;
		    umbrella.z = 0.5 * (pit->z + nit->z) - it->z;
		    it->x = it->x + G3CS_WII_DepthNoiseReduction * umbrella.x;
		    it->z = it->z + G3CS_WII_DepthNoiseReduction * umbrella.z;
        }

        pit = it;
        it = nit;
        nit++;
    }

	iterations++;
}

void SUBSPLINE::Clear () {
	Points.clear ();

	if (Compiling == 2)
		glDeleteLists (DLID, 1);

	Compiling = 0;
	DLID = 0;
}

//==============================================================================
// Renders a spline
//==============================================================================
void DRAWN_SPLINE::Render (float aElapsedTime) {
    G3::GUI::PROFILER prof ("Spline::Render");

    // Lock the spline so that no subsplines would be added during the rendering
    boost::mutex::scoped_lock lock( m_mutex );

    NumLinesRendered = 0;
    NumPointsRendered = 0;
    NumSegmentsRendered = 0;
    NumSubSplines = SubSplines.size ();

    if (!UseParticles) {
        glDisable (GL_TEXTURE_2D);

        for (uint i=0; i<SubSplines.size (); i++) {
            // Don't filter the most recent subspline
            if (i < SubSplines.size () - 1)
                SubSplines [i].Filter ();

            SubSplines [i].Render (aElapsedTime);
        }

        glEnable (GL_TEXTURE_2D);
    } else {
        glDisable (GL_LIGHTING);

        PSys.Update( aElapsedTime );
        PSys.Render();

        glEnable (GL_LIGHTING);
    }
}

//==============================================================================
// Loads a spline from a file
//==============================================================================
bool DRAWN_SPLINE::Load (const std::string &aFilename) {
    unsigned long i = 0, j = 0, numSubSplines = 0, numPoints = 0;
    SUBSPLINE subSpline;
    G3::VEC3F point;

	if (!G3::FileExists (aFilename))
		return false;

	// Give the subsplines the same radiuses with the master spline
	subSpline.Radius = Spline.Radius;

    G3::MainLog.Report ("Loading a spline from \"%s\"..\n", aFilename.c_str ());

    G3::CISTREAM cStream;
    G3::ZIPSTREAM zStream (&cStream);

	// Open for the best decompression
    if (!zStream.Open (ZSTREAM_INFLATE, 9)) {
        G3::MainLog.Report ("Failed to open \"%s\" for decompression..\n", aFilename.c_str ());
        return false;
    }

	// Uncompress into the stream
    if (!zStream.InflateFromFile (aFilename)) {
        G3::MainLog.Report ("Failed to inflate from file \"%s\"..\n", aFilename.c_str ());
        zStream.Close ();
        return false;
    }

	// Read the number of subsplines
    if (!cStream.Read ((byte *) &numSubSplines, sizeof (unsigned long), false)) {
        G3::MainLog.Report ("Failed to read the number of subsplines..\n");
        zStream.Close ();
        return false;
    }

	// Read all the subsplines
    for (i=0; i<numSubSplines; i++) {
		// Push an empty subspline
        SubSplines.push_back (subSpline);

		// Read the number of points in this subspline
        if (!cStream.Read ((byte *) &numPoints, sizeof (unsigned long))) {
            G3::MainLog.Report ("Failed to read the number of points in the %d. subspline..\n", i);
            zStream.Close ();
            return false;
        }

		// Read all the points
        for (j=0; j<numPoints; j++) {
			// Read the point
            if (!cStream.Read ((byte *) &point, sizeof (G3::VEC3F))) {
                G3::MainLog.Report ("Failed to read the %d. point in the %d. subspline..\n", j, i);
                zStream.Close ();
                return false;
            }

			// Append it to the list
            SubSplines [SubSplines.size () - 1].Points.push_back (point);

            // Update the particle system position
            PSys.Position = point;
            // And emit a particle
            PSys.Emit();
        }

		//! \note Reduces performance
		//SubSplines [SubSplines.size () - 1].Compile ();
    }

	// Close the ZipStream
    zStream.Close ();

    G3::MainLog.Report ("The spline \"%s\" has been loaded successfully..\n", aFilename.c_str ());
    return true;
}

//==============================================================================
// Saves a spline into a file
//==============================================================================
bool DRAWN_SPLINE::Save (const std::string &aFilename) {
    G3::CISTREAM cStream;
    G3::ZIPSTREAM zStream (&cStream);
    unsigned long i = 0, j = 0, numSubSplines = SubSplines.size (), numPoints = 0;

    G3::MainLog.Report ("Saving the spline to \"%s\"..\n", aFilename.c_str ());

	// Open the file for the best compression available
    if (!zStream.Open (ZSTREAM_DEFLATE, 9)) {
        G3::MainLog.Report ("Failed to open \"%s\" for compression..\n", aFilename.c_str ());
        return false;
    }

	// Buffer the number of subsplines
    if (!cStream.Buffer ((byte *) &numSubSplines, sizeof (unsigned long))) {
        G3::MainLog.Report ("Failed to write the number of subsplines..\n");
        zStream.Close ();
        return false;
    }

	// Buffer all of the subsplines
    for (i=0; i<numSubSplines; i++) {
        numPoints = SubSplines [i].Points.size ();

		// Buffer the number of points in the subspline
        if (!cStream.Buffer ((byte *) &numPoints, sizeof (unsigned long))) {
            G3::MainLog.Report ("Failed to write the number of points in the %d. subspline..\n", i);
            zStream.Close ();
            return false;
        }

		// Buffer all the points
        std::list<G3::VEC3F>::iterator it = SubSplines [i].Points.begin();
        while( it != SubSplines [i].Points.end() ) {
			// Buffer the point
            if (!cStream.Buffer ((byte *) (&(*it)), sizeof (G3::VEC3F))) {
                G3::MainLog.Report ("Failed to write the %d. point of the %d. subspline..\n", j, i);
                zStream.Close ();
                return false;
            }
            // Take the next point
            it++;
        }
    }

    G3::MainLog.Report ("Written: %ld\n", cStream.Tell (G3::STREAM::STREAM_END));

	// Seek to the beginning of the file
    cStream.Seek (0, G3::STREAM::STREAM_BEGINNING);

	// Compress the whole stream into the file
    if (!zStream.DeflateToFile (aFilename)) {
        G3::MainLog.Report ("Failed to write to file \"%s\"..\n", aFilename.c_str ());
        zStream.Close ();
        return false;
    }

	// Close the file
    zStream.Close ();

    G3::MainLog.Report ("The spline \"%s\" has been saved successfully..\n", aFilename.c_str ());
    return true;
}

void StartNew() {
    Spline.SubSplines.clear ();
    Spline.PSys.Clear();

    frame.Renderer.Camera.Reset (G3CS_Camerapos);
    frame.Renderer.Camera.FocusOn (G3::VEC3F (0, 0, 0));
}

void ToggleDrawMode() {
    UseParticles ^= 1;
    if (UseParticles)
        DrawModeIndicator.SetImage( hFogMode );
    else
        DrawModeIndicator.SetImage( hTorusMode );
}
